-
-
Notifications
You must be signed in to change notification settings - Fork 5
Handle floating promises #3492
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Handle floating promises #3492
Conversation
|
Thanks for tackling this @siltomato. One major concern I have is that it's going to create a slew of merge conflicts with #3199, which is already big, difficult, and affects async/await in a lot of places. Maybe we should merge the two instances you found that you think should be awaited, and then have the rest of them updated after #3199 to reduce conflicts (or at least to simplify the conflicts, since adding void is a lot simpler than the other PR's changes)? |
c001568 to
45423d5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tested merging this branch with #3199, and there were merge conflicts in just 5 files that were pretty straightforward to fix. But just for peace of mind, there's no harm in just waiting to merge this until that branch is merged.
Reviewable status: 0 of 70 files reviewed, all discussions resolved
|
@siltomato 5 straightforward fixes is very manageable. Let's proceed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR addresses lint no-floating-promises warnings throughout the codebase by adding void to promises that are intentionally fire-and-forget. Two instances in the editor components use async/await for improved readability, and one instance in realtime-doc.ts uses await to maintain data consistency during deletion operations.
Key Changes
- Added
voidprefix to floating promises to maintain existing fire-and-forget behavior while satisfying linting requirements - Converted
onLeafNodeClick()in lynx insights panel to async/await pattern for better readability - Changed
delete()method in realtime-doc.ts to await adapter deletion for data consistency
Reviewed Changes
Copilot reviewed 69 out of 69 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/xforge-common/models/realtime-doc.ts |
Changed delete() method to await adapter deletion for consistency |
src/app/translate/editor/lynx/insights/lynx-insights-panel/lynx-insights-panel.component.ts |
Converted onLeafNodeClick() to async/await pattern |
| Multiple service and component files | Added void prefix to floating promises across the application |
| async delete(): Promise<void> { | ||
| this.adapter.delete(); | ||
| await this.adapter.delete(); | ||
| await this.updateOfflineData(); | ||
| await this.realtimeService.onLocalDocUpdate(this); | ||
| } |
Copilot
AI
Oct 8, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The change to await this.adapter.delete() is correct for maintaining data consistency. However, consider adding error handling to ensure the subsequent operations don't execute if the adapter deletion fails, which could leave the document in an inconsistent state.
Copilot generated this review using guidance from repository custom instructions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the other pull request which deals with realtime doc cleanup, the order in which the disposal methods are called turns out to be very complicated and very important. I would suggest that we leave this line unchanged and fix it in the other pull request in the interest of simplicity. Basically, the situation is just too complicated for me to mentally untangle and be certain that this change is going to work with the changes in the other PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, changed await to void.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest we leave this line unchanged. The change might be correct, and the existing behavior could even be wrong. I'd rather have a lint warning reminding us to deal with it. Marking it void is essentially saying "a developer looked at this and conclusively determined that we don't want to await this", which is definitely not correct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Changed.
.../src/app/translate/editor/lynx/insights/lynx-insights-panel/lynx-insights-panel.component.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewable status: 0 of 70 files reviewed, all discussions resolved (waiting on @Nateowami)
.../src/app/translate/editor/lynx/insights/lynx-insights-panel/lynx-insights-panel.component.ts
Show resolved
Hide resolved
| async delete(): Promise<void> { | ||
| this.adapter.delete(); | ||
| await this.adapter.delete(); | ||
| await this.updateOfflineData(); | ||
| await this.realtimeService.onLocalDocUpdate(this); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, changed await to void.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Nateowami dismissed @copilot from a discussion.
Reviewable status: 0 of 70 files reviewed, all discussions resolved
| async delete(): Promise<void> { | ||
| this.adapter.delete(); | ||
| await this.adapter.delete(); | ||
| await this.updateOfflineData(); | ||
| await this.realtimeService.onLocalDocUpdate(this); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest we leave this line unchanged. The change might be correct, and the existing behavior could even be wrong. I'd rather have a lint warning reminding us to deal with it. Marking it void is essentially saying "a developer looked at this and conclusively determined that we don't want to await this", which is definitely not correct.
0bb903a to
ef08444
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewable status: 0 of 70 files reviewed, all discussions resolved (waiting on @Nateowami)
| async delete(): Promise<void> { | ||
| this.adapter.delete(); | ||
| await this.adapter.delete(); | ||
| await this.updateOfflineData(); | ||
| await this.realtimeService.onLocalDocUpdate(this); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few things to clean up. Otherwise, thanks for the improvement.
@RaymondLuong3 reviewed 62 of 70 files at r1, 7 of 7 files at r2, 1 of 1 files at r4, all commit messages.
Reviewable status: all files reviewed, 23 unresolved discussions (waiting on @siltomato)
src/SIL.XForge.Scripture/ClientApp/src/app/my-projects/my-projects.component.ts line 130 at r4 (raw file):
void this.router.navigate(['projects', projectId]); } catch { void this.noticeService.show(this.i18n.translateStatic('my_projects.failed_to_join_project'));
No longer necessary if show() and showError() are synchronous.
src/SIL.XForge.Scripture/ClientApp/src/app/checking/question-dialog/question-dialog.service.ts line 47 at r4 (raw file):
} if (!(await this.canCreateAndEditQuestions(config.projectId))) { void this.noticeService.show(this.transloco.translate('question_dialog.add_question_denied'));
This would not need to be voided if show is no longer asynchronous.
Code quote:
void this.noticeService.show(this.transloco.translate('question_dialog.add_question_denied'));src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation-steps/draft-generation-steps.component.ts line 554 at r4 (raw file):
} else { if (!this.onlineStatusService.isOnline) { void this.noticeService.show(this.i18n.translateStatic('draft_generation.offline_message'));
No longer necessary if show is synchronous
Code quote:
void this.noticeService.show(src/SIL.XForge.Scripture/ClientApp/src/app/shared/audio-recorder-dialog/audio-recorder-dialog.component.ts line 235 at r4 (raw file):
if (error.code === DOMException.NOT_FOUND_ERR) { void this.noticeService.show(this.i18n.translateStatic('checking_audio_recorder.mic_not_found'));
No longer necessary if show() is synchronous.
Code quote:
void this.noticeService.show(src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-usfm-format/draft-usfm-format.component.ts line 221 at r4 (raw file):
} catch (err) { console.error('Error occurred while saving draft format', err); void this.noticeService.showError(this.i18n.translateStatic('draft_usfm_format.failed_to_save'));
No longer necessary if showError() is synchronous
Code quote:
oid this.noticeService.showError(src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor-draft/editor-draft.component.ts line 304 at r4 (raw file):
this.userAppliedDraft = true; } catch (err) { void this.noticeService.showError(this.i18n.translateStatic('editor_draft_tab.error_applying_draft'));
No longer necessary if show is synchronous
Code quote:
void this.noticeService.showError(src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor.component.ts line 1806 at r4 (raw file):
this.translator = undefined; if (this.translationSuggestionsEnabled) { void this.noticeService.show(this.i18n.translateStatic('editor.verse_too_long_for_suggestions'));
No longer necessary if show() is synchronous.
Code quote:
void this.noticeService.show(src/SIL.XForge.Scripture/ClientApp/src/app/checking/checking/checking-answers/checking-answers.component.ts line 457 at r4 (raw file):
}); } else if (likeAnswerResponse === LikeAnswerResponse.DeniedOwnAnswer) { void this.noticeService.show(this.i18n.translateStatic('checking_answers.cannot_like_own_answer'));
this should no longer need to be voided. And below.
Code quote:
void this.noticeService.show(src/SIL.XForge.Scripture/ClientApp/src/app/users/collaborators/collaborators.component.ts line 275 at r4 (raw file):
this._userRows = userRows.concat(invitees); } catch { void this.noticeService.show(this.i18n.translateStatic('collaborators.problem_loading_invited_users'));
No longer necessary if show() is synchronous.
Code quote:
void this.noticeService.show(tsrc/SIL.XForge.Scripture/ClientApp/src/app/machine-api/remote-translation-engine.ts line 108 at r4 (raw file):
); } else { void this.noticeService.showError(this.i18n.translateStatic('error_messages.failed_to_retrieve_suggestions'));
No longer necessary if showError() is synchronous.
Code quote:
void this.noticeService.showError(src/SIL.XForge.Scripture/ClientApp/src/app/checking/import-questions-dialog/import-questions-dialog.component.ts line 503 at r4 (raw file):
data ) as MatDialogRef<ImportQuestionsConfirmationDialogComponent, ImportQuestionsConfirmationDialogResult>; const result: ImportQuestionsConfirmationDialogResult | undefined = await lastValueFrom(dialogRef.afterClosed());
It looks like this is the only place where an await is used in this method. We could just subscribe to the afterClosed() observable and run the logic in the listener and this method wouldn't need to be asynchronous.
Code quote:
await lastValueFrom(dialogRef.afterClosed());src/SIL.XForge.Scripture/ClientApp/src/app/checking/checking/checking.component.ts line 1291 at r4 (raw file):
const answerIndex = this.getAnswerIndex(answer); if (answerIndex >= 0) { void activeQuestionDoc
This might be one of those places where we would benefit from awaiting the promise. Assuming that refresh summary depends on the number of answers in the question.
Code quote:
void activeQuestionDocsrc/SIL.XForge.Scripture/ClientApp/src/app/sync/sync.component.ts line 111 at r4 (raw file):
) { if (this.projectDoc.data.sync.lastSyncSuccessful) { void this.noticeService.show(
No longer necessary if show() is synchronous.
Code quote:
void this.noticeService.show(src/SIL.XForge.Scripture/ClientApp/src/xforge-common/auth.service.ts line 216 at r4 (raw file):
} async expireToken(): Promise<void> {
This can be made a synchronous method.
Code quote:
async expireToken(): Promise<void> {src/SIL.XForge.Scripture/ClientApp/src/xforge-common/exception-handling.service.ts line 188 at r4 (raw file):
ngZone.run( () => void noticeService.showError(this.i18n.translateStatic('exception_handling_service.network_request_failed'))
No longer necessary if show() is synchronous. And 1 instance below.
Code quote:
void noticeService.showError(tsrc/SIL.XForge.Scripture/ClientApp/src/xforge-common/notice.service.ts line 50 at r4 (raw file):
} async show(message: string): Promise<void> {
Same as below.
Code quote:
async show(message: string): Promise<void> {src/SIL.XForge.Scripture/ClientApp/src/xforge-common/notice.service.ts line 54 at r4 (raw file):
} async showError(
Same as below
Code quote:
async showError(src/SIL.XForge.Scripture/ClientApp/src/xforge-common/notice.service.ts line 62 at r4 (raw file):
} private async showSnackBar(
You can remove the async from this function and update the Promise return value to be void.
Code quote:
private async showSnackBar(src/SIL.XForge.Scripture/ClientApp/src/app/translate/editor/editor-history/history-chooser/history-chooser.component.ts line 145 at r4 (raw file):
async revertToSnapshot(): Promise<void> { if (!this.onlineStatusService.isOnline) { void this.noticeService.show(this.i18n.translateStatic('history_chooser.please_connect'));
No longer necessary if show and showError is synchronous. And 3 instances below.
Code quote:
void this.noticeService.show(src/SIL.XForge.Scripture/ClientApp/src/app/translate/training-progress/training-progress.component.ts line 144 at r4 (raw file):
// training completed successfully if (this.trainingProgressClosed) { void this.noticeService.show(
No longer necessary if show() is synchronous.
Code quote:
oid this.noticeService.show(src/SIL.XForge.Scripture/ClientApp/src/app/shared/share/share-control.component.ts line 189 at r4 (raw file):
} void this.noticeService.show(message);
No longer necessary if show is synchronous.
Code quote:
void this.noticeService.show(message);src/SIL.XForge.Scripture/ClientApp/src/xforge-common/user.service.ts line 127 at r4 (raw file):
await this.updateAvatarFromDisplayName(); } catch { void this.noticeService.showError(translate('error_messages.failed_to_update_avatar'));
No longer necessary if showError is synchronous.
Code quote:
void this.noticeService.showError(src/SIL.XForge.Scripture/ClientApp/src/app/translate/draft-generation/draft-generation.service.ts line 78 at r4 (raw file):
} void this.noticeService.showError(this.i18n.translateStatic('draft_generation.temporarily_unavailable'));
No longer necessary if showError() is synchronous. And 6 instances below.
Code quote:
void this.noticeService.showError(
This PR fixes all lint
no-floating-promiseswarnings. All files used thevoidmethodology to handle the floating promises (maintaining existing fire-and-forget behavior) except for the two instances described below.In the
lynx-insight-panel.component.tsonLeafNodeClick()function, the promise chain is changed to async/await. This is more of a readability change than functional, as, in both cases, the nav completes before the call to update display state.In the
realtime-doc.tsdelete()function:await this.adapter.delete()was chosen instead ofvoid this.adapter.delete()with the idea to maintain data consistency in the case that theadapter.delete()call fails. Please review if this is the correct approach.This change is